package no.rmz.chez.chess.board;

import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;

import no.rmz.chez.chess.movement.Direction;
import no.rmz.chez.chess.pieces.AlgebraicChessNotationElement;

/**
 * Chess coordinates.
 */
public  class Coord implements Comparable<Coord>, AlgebraicChessNotationElement {

    /**
     * Coordinates.  Has values [0..7]
     */
    private int x;
    private int y;

    private final static  Character[] iToCMap =
            new Character[8];
    static {
        iToCMap[0] = 'a';
        iToCMap[1] = 'b';
        iToCMap[2] = 'c';
        iToCMap[3] = 'd';
        iToCMap[4] = 'e';
        iToCMap[5] = 'f';
        iToCMap[6] = 'g';
        iToCMap[7] = 'h';
    }

    private static Character iToC(final int i) {
        return iToCMap[i];
    }


    /**
     * Override default object print method.
     */
    @Override
    public  final String toString() {
       return "[Coord x = " + getX() + ", y = " + getY() + " (" + getACNRepresentation() + ")]";
    }

    @Override
    public String getACNRepresentation() {
        return String.format("%c%d", iToC(getX()), getY());
    }


    /**
     * The minimum allowed coordinate.
     */
    public final  static int MIN_BOARD_INDEX = 0;

    /**
     * The maximum allowed coordinate.
     */
    public final  static int MAX_BOARD_INDEX = 7;

    /**
     * Return true iff the coordinate is inside the valid
     * boundaries for a chessboard.
     *
     * @param x
     * @param y
     * @return
     */
    public static boolean isInsideChessboard(int x, int y) {
        return  (  MIN_BOARD_INDEX <= x && x <= MAX_BOARD_INDEX 
                && MIN_BOARD_INDEX <= y && y <= MAX_BOARD_INDEX);
    }


    /**
     * Return true iff this coordinate is inside a chessboard.
     * @return if this coordiate is inside a chessboard.
     */
    public final boolean isInsideChessboard() {
        return isInsideChessboard(x, y);
    }

    /**
     * A map containing all the coordinates inside a
     * chessboard.
     */
    private static Map<Coord, Coord>  boardCoords = null;

    /**
     *   Populate boarCoords with real coordinates.
     *
     */
    private static void populateBoardCoord() {
        boardCoords = new TreeMap<>();
        for (int x = MIN_BOARD_INDEX; x <= MAX_BOARD_INDEX; x++) {
            for (int y = MIN_BOARD_INDEX; y <= MAX_BOARD_INDEX; y++) {
                final Coord c = new Coord(x, y);
                boardCoords.put(c, c);
            }
        }
    }

    /**
     * Find the board-coordinate, in the board map, that
     * corresponds to the present coordinate with an added
     * delta going in some direction.
     *
     * @param c
     * @return
     */
    private Coord add(final Coord c) {
        return getCoord(getX() + c.getX(), getY() + c.getY());
    }

    /**
     * If a knight is located at the current coordinate, where
     * can it possibly jump?
     */
    private Collection<Coord> knightsDestinations =
        new LinkedList<>();


    /**
     * If a knight is located at the current coordinate, where
     * can it possibly jump?
     */
    public Collection<Coord> getKnightsDestinations() {
        return knightsDestinations;
    }

    /**
     * Compute all possible jumps for a knight.
     */
    private static void addKnightsMoves() {
        for (final Coord c  : boardCoords.values()) { 
            for (final Direction d : Direction.getKnightsDirections()) {
                final Coord target = c.add(d);
                if (target != null) {
                    c.getKnightsDestinations().add(target);
                }
            }
        }
    }


    /**
     * Get all the board coordinates.
     * @return
     */
    static synchronized  Map<Coord, Coord> getBoardCoords () {   
        if (boardCoords == null) {
            populateBoardCoord();
            addKnightsMoves();
        }
        return boardCoords;
    }


    /**
     * When located at the present coordinate, where
     * do you end up when performign a step in the
     * selected direction?   Return a coordinate
     * object if the result is a valid coordinate
     * on the chessboard, return null if it�s outside
     * the board.
     *
     * @param d a direction.
     * @return get a coordinate after adding a step in a direction.
     */
    public final Coord stepInDirection(final Direction d) {
        return add(d);
    }

 
    /**
     *  Add a vector to the current coordinate.
     * @param dx the delta x
     * @param dy the delta y
     * @return a coordinate
     */
    public final Coord add(final int dx, final int dy) {
        return getCoord(getX() + dx, getY() + dy);
    }


    /**
     * Get a coordinate on the board.
     * @param x
     * @param y
     * @return
     */
    public static synchronized Coord getCoord(final int x, final int y) {
        return getBoardCoords().get(new Coord(x, y));
    }

    protected Coord(final int x, final int y) {
        super();
        this.x = x;
        this.y = y;
    }


    /**
     * Compare coordinates using lexicographic
     * comparison.
     * @param other the object we're comparing ourselves to
     * @return -1 if we're less thatn, +1 if we're greater than or 0 of we're equal to the other object.
     */
    @Override
    public final int compareTo(final Coord other) {
        int r = Integer.signum(x - other.x);
        if (r != 0) {
            return r;
        } else {
            return Integer.signum(y - other.y);
        }
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj instanceof  Coord) {
            return compareTo((Coord)obj) == 0;
        } else {
            return super.equals(obj);
        }
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 73 * hash + this.x;
        hash = 73 * hash + this.y;
        return hash;
    }

    public int asIndex() {
        return this.x + MAX_BOARD_INDEX * this.y;
    }
}
